require 'coord'
require 'plot'

class Courier
	attr_accessor :goods, :destination, :location, :speed, :origin
	
	def initialize(speed, location)
		@speed = speed  # units per second
		@available = true
		@goods = ""
		@dist = 0
		@location = location
		
		@picked_up = false
	end
	
	def available?
		@available
	end
	
	def picked_up?
		@picked_up
	end
	
	def deliver(what, origin, dest)
		@available = false
		@goods = what
		@start_pos = @location.clone
		@origin = origin
		@destination = dest
		@dist = @location.distance_to(origin.location)
		#puts "* courier leaving #{@location} to head for #{origin.name} at #{origin.location}"
		#print "S" + "-"*@start_pos.distance_to(@origin.location) + "M" + "-"*@start_pos.distance_to(@destination.location) + "F"
		#puts
		@last_work = Time.now
		@last_stop = Time.now		
	end
	
	def work
		delta_t = Time.now - @last_stop
		eta = @dist / @speed
		# Update Courier's position
		temp_dest = @picked_up ? @destination.location : @origin.location
		temp_start =  @picked_up ?  @origin.location : @start_pos
		@location.x = temp_start.x - (temp_start.x - temp_dest.x)/eta * delta_t
		@location.y = temp_start.y - (temp_start.y - temp_dest.y)/eta * delta_t
		@location.z = temp_start.z - (temp_start.z - temp_dest.z)/eta * delta_t
		
		if delta_t > eta
			if @picked_up
				@destination.deliver(@goods)
				#puts "* courier delivered #{goods} to #{@destination.name}"
				#@location = @destination.location.clone
				@available = true
				@picked_up = false
			else
				@origin.pickup(@goods)
				#puts "* courier picked up #{goods} from #{@origin.name}"
				@picked_up = true
				#@location = @origin.location.clone
				@dist = @location.distance_to(@destination.location)
			end
			@last_stop = Time.now	
		end
		
		#puts "O: #{@origin.location} D: #{@destination.location} L: #{@location}"
		@last_work = Time.now
	end
	
end

class City
	attr_accessor :name
	
	def initialize(name)
		@graph = Plot.new(40,40,2)
		@name = name
		@buildings = Array.new
		@founded = Time.new	
		@delivery_center = DeliveryCenter.new("MPS", Coord.new(0,0,0))	
		3.times { @delivery_center.add_courier }
	end
	
	def build(building)
		@buildings << building
	end
	
	def run
		while(true)
			@buildings.each do |cur_b|
				@graph.add(cur_b.location, cur_b.name[0,1])
				@buildings.each do |b|
					#puts " -- #{cur_b.name} produces #{cur_b.produces} and #{b.name} demands #{b.demand.empty? ? "Nothing" : b.demand}."
					#puts " #{b.demand.include?(cur_b.produces)} and #{cur_b.cache[cur_b.produces]}"
					if(b.demand.include?(cur_b.produces) && (cur_b.cache[cur_b.produces] - @delivery_center.num_pickups(cur_b)) > 0)
						if( @delivery_center.num_deliveries(cur_b.produces, b) + (b.cache[cur_b.produces] || 0) < 8)
							result = @delivery_center.request(cur_b.produces, cur_b, b)
							#puts "Delivery of #{cur_b.produces} from #{cur_b.name} to #{b.name} was #{result ? "Accepted" : "Queued"}"
						end
					end
				end
			end					
			
			@delivery_center.work
			@graph.add(@delivery_center.location, @delivery_center.name[0,1])
			@delivery_center.each_courier do |c|
				@graph.add(c.location, c.picked_up? ? 'O' : 'o')
			end
			
			@buildings.each do |b|
				b.work
				#print "#{b.name} has "
				#b.cache.each_pair {|k, v| print "#{v} #{k}, "}
				#puts ""
			end
			
			
			@graph.plot
			@graph.clear
			gets
		end
	end
	
end

class Structure
	attr_accessor :name, :cache, :demand, :produces, :location, :speed
	
	def initialize(name, produce, demand, location)
		@name = name
		@cache = Hash.new
		@cache[produce] = 0
		@demand = demand
		@produces = produce	
		@location = location
		@built = Time.now
		@last_work = Time.now	
		@speed = 2  # minimum number of seconds between production
	end
	
	def work
		if (Time.now - @last_work >= @speed)
			matches = 0
			@demand.each  { |d| matches += 1 if (@cache.has_key?(d) && @cache[d] > 0) }	
			if (matches == @demand.length && @cache[@produces] < 8)
				#puts " #{name} did some work"
				@cache[@produces] = 1 + (@cache[@produces] || 0) 
				@demand.each  do |d| 
					if (@cache.has_key?(d) && @cache[d] > 0)
						@cache[d] -= 1 
					end
				end
			end
			@last_work = Time.now
		end
	end
	
	def deliver(resource)
		if (!@cache.has_key?(resource) || @cache[resource] < 8)
			@cache[resource] = 1 + (@cache[resource] || 0)
			return true
		else
			return false	
		end
	end
	
	def pickup(resource)
		@cache[resource] -= 1 if (@cache[resource] >= 1) 
	end
	
end

class DeliveryCenter < Structure

	def initialize(name, location)
		super(name, "", [], location)
		@couriers = Array.new
		@queue = Array.new
	end
	
	def add_courier()
		@couriers << Courier.new(2.0, @location.clone)
	end
	
	def each_courier
		@couriers.each { |c| yield c }	
	end
	
	def num_pickups(from)
		count = 0
		@couriers.each { |c| count += 1 if (!c.available? && !c.picked_up? && c.origin == from) }
		@queue.each { |q| count += 1 if (q[1] == from) }
		return count
	end 
	
	def num_deliveries(good, where)
		count = 0
		@couriers.each { |c| count += 1 if (!c.available? && c.destination == where && c.goods == good) }
		@queue.each { |q| count += 1 if (q[0] == good && q[2] == where) }
		return count		
	end
	
	def available_couriers
		count = 0
		@couriers.each { |c| count += 1 if c.available? }
		return count
	end
	
	def request(product, origin, dest)
		req_met = false
		@couriers.each do |c|
			if(c.available? && !req_met)
				c.deliver(product, origin, dest)
				req_met = true
			end
		end
		
		@queue << [product, origin, dest] unless req_met
		
		return req_met
	end
	
	def work()
		@couriers.each { |c| c.work unless c.available? }
		available_couriers.times do
			q = @queue.shift 
			request(q[0], q[1], q[2]) unless q == nil
		end
		
		#puts "#{name} has #{@queue.length} items waiting to be delivered and #{available_couriers} idle couriers"
		@queue.each_index do |i|
			#puts "#{i}\t#{@queue[i][0]}\t#{@queue[i][1].name}\t#{@queue[i][2].name}"
		end
		
	end
end

if $0 == __FILE__
	utopia = City.new("Utopia")
	
	utopia.build( Structure.new("Woodcutter1", "Wood", [], Coord.new(5,5,3)) )
	utopia.build( Structure.new("Woodcutter2", "Wood", [], Coord.new(2,10,2)) )
	utopia.build( Structure.new("Sawmill", "Boards", ["Wood"], Coord.new(10,2,1)) )
	utopia.build( Structure.new("Furniture Store", "Chairs", ["Boards"], Coord.new(9,13,2)) )
	utopia.run
end